/* lfsdel.c  -  lfsdel */

#include <xinu.h>
#include <string.h>

/*------------------------------------------------------------------------
 * lfsdel  --  delete a file from the directory and free 
 * 			the disk blocks holding the content of the file
 *------------------------------------------------------------------------
 */
 
struct	lflcblk	*lfptr_del;		/* ptr to open file table entry	*/
struct	lfiblk	*iblock;		/* buffer for one index block	*/
ibid32	ifree;				/* start of index blk free list	*/
ibid32	firstib;			/* first index blk of the file	*/
ibid32	nextib;				/* walks down list of the	*/
							/*   file's index blocks	*/
dbid32	nextdb;				/* next data block to free	*/

int32 rec_del	(
	char	*filename,
	struct 	ldentry	*lf_dirptr
	)
{
	char	*from, *to;				/* ptrs used during copy		*/
	char	*nam, *cmp;			/* ptrs used during comparison	*/
	char	*dname, *fname;		/* dir and file name in recursion	*/
	char	*pname;				/* file name used in recursion	*/
	int32	i, j, k;			/* general loop index			*/
	int32	pos;				/* where to add new dir entry	*/
	char	path[2][LF_NAME_LEN];	/* path names	*/
	struct	ldentry	*ldptr;		/* ptr to dir entry for opening file*/
	struct	ldentry *ldpaptr;	/* ptr to dir entry used in search	*/
	bool8	found;				/* was the name found?			*/
	uint32	pathnum;			/* record # dirs along the path	*/
	int32	retval;				/* value returned from function	*/
	
	/* parse the file name into directory along the path */
	from = filename;
	j = k = 0;
	for(i=1; from[i] != NULLCH; i++) {
		if(from[i] != '/') {
			if (j < 2) {
				path[j][k] = from[i];
			}
			k ++;
		}
		else {
			if (j < 2) {
				path[j][k] = NULLCH;
				j ++;
			}
			k = 0;
		}
	}
	if (j < 2) {
		path[j][k] = NULLCH;
	}
	pathnum = j;
	dname = path[0];
	fname = path[1];
	
	////kprintf("\r\nLoad dir %s into memory\r\n", dname);
	lfptr_del->lfdirptr = lf_dirptr;	/* point to dir's directory entry */
	lfptr_del->lfpos     = 0;
	to = lfptr_del->lfname;
	from = dname;
	while ( (*to++ = *from++) != NULLCH ) {
		;
	}
	lfptr_del->lfinum    = LF_INULL;
	lfptr_del->lfdnum    = LF_DNULL;
	lfptr_del->lfbyte = &lfptr_del->lfdblock[LF_BLKSIZ];
	lfptr_del->lfibdirty = FALSE;
	lfptr_del->lfdbdirty = FALSE;
	signal(Lf_data.lf_mutex);
	/* Search /a directory to see if b exists 	*/
	found = FALSE;
	ldpaptr = (struct	ldentry	*)getmem(sizeof(struct ldentry));
	for (i=0; i<((lfptr_del->lfdirptr)->ld_size)/sizeof(struct ldentry); i++) {
		seek(lfptr_del->lfdev, i*sizeof(struct ldentry));
		read(lfptr_del->lfdev, (char *)ldpaptr, sizeof(struct ldentry));
		nam = fname;
		cmp = ldpaptr->ld_name;
		while(*nam != NULLCH) {
			if (*nam != *cmp) {
				break;
			}
			nam++;
			cmp++;
		}
		if ( (*nam==NULLCH) && (*cmp==NULLCH) ) { /* name found	*/
			found = TRUE;
			ldptr = ldpaptr;
			break;
		}
	}
	pos = i;
	/* Case #1 - file b is not in directory a		*/
	if (! found) {
		/* open file direcotry in root directory, e.g. /a		*/
		signal(Lf_data.lf_mutex);
		//kprintf("No such file exits!\r\n");
		close(lfptr_del->lfdev);
		return SYSERR;
	}
	/* Case #2 - file b is in directory a		*/
	else {
		////kprintf("Case #2 - file %s is in directory %s\r\n", fname, dname);
		/* only one dir along the path, e.g. /a/b done		*/
		if (pathnum == 1) {
			//kprintf("only /%s/%s, no more path\r\n", dname, fname);
			if (ldpaptr->ld_type == LF_TYPE_DIR) {
				signal(Lf_data.lf_mutex);
				//kprintf("Cannot delete dir-type file, use rmdir\r\n");
				close(lfptr_del->lfdev);
				return SYSERR;
			}
			/* Take steps to delete file b in directory	a	*/
			if (ldptr->ld_size != 0) {
				////kprintf("Free i-blk and d-blk for file %s\r\n", fname);
				/* Obtain ID of first index block on free list	*/
				ifree = Lf_data.lf_dir.lfd_ifree;
				/* Record file's first i-block */
				firstib = ldptr->ld_ilist;
				/* Walk along index block list, disposing of each data block	*/
				/*  and clearing the corresponding pointer.		*/
				iblock = (struct lfiblk *)getmem(sizeof(struct lfiblk));
				for (nextib=firstib; nextib!=ifree; nextib=iblock->ib_next) {
					/* Obtain a copy of current index block from disk	*/
					lfibget(Lf_data.lf_dskdev, nextib, iblock);
					/* Free each data block in the index block		*/
					for (i=0; i<LF_IBLEN; i++) {	/* for each d-block	*/
						/* Free the data block */
						nextdb = iblock->ib_dba[i];
						if (nextdb != LF_DNULL) {
							lfdbfree(Lf_data.lf_dskdev, nextdb);
						}
						/* Clear entry in i-block for this d-block	*/
							iblock->ib_dba[i] = LF_DNULL;
					}
					/* Clear offset (just to make debugging easier)		*/
					iblock->ib_offset = 0;
					/* For the last index block on the list, make it point	*/
					/*	to the current free list			*/
					if (iblock->ib_next == LF_INULL) {
						iblock->ib_next = ifree;
					}
					/* Write cleared i-block back to disk */
					lfibput(Lf_data.lf_dskdev, nextib, iblock);
				}
				/* Last index block on the file list now points to first node	*/
				/*   on the current free list. 	*/
				Lf_data.lf_dir.lfd_ifree = firstib;
			}
			/* delete b's dir entry in directory a	*/
			//kprintf("Delete %s's dir entry in %s\r\n", fname, dname);
			signal(Lf_data.lf_mutex);
			ldpaptr = (struct ldentry *)getmem(sizeof(struct ldentry));
			for (i=pos; i<(((lfptr_del->lfdirptr)->ld_size)/\
			sizeof(struct ldentry))-1; i++) {
				seek(lfptr_del->lfdev, (i+1)*sizeof(struct ldentry));			
				read(lfptr_del->lfdev, (char *)ldpaptr, sizeof(struct ldentry));
				seek(lfptr_del->lfdev, i*sizeof(struct ldentry));
				write(lfptr_del->lfdev, (char *)ldpaptr, sizeof(struct ldentry));
			}
			seek(lfptr_del->lfdev, i*sizeof(struct ldentry));
			from[0] = (char)EOF;
			write(lfptr_del->lfdev, from, 1);
			(lfptr_del->lfdirptr)->ld_size = (lfptr_del->lfdirptr)->ld_size - \
			sizeof(struct ldentry);
			freemem((char *)iblock, sizeof(struct lfiblk));
			return OK;
		}
		/* more than 1 dir along the path, e.g. /a/b/c/d...	*/
		else if (pathnum > 1) {
			////kprintf("more dires after /%s/%s\r\n", fname, dname);
			pname = strchr(filename+1, '/');
			//kprintf("recursively delete %s begins\r\n", pname);
			retval = rec_del(pname, ldptr);
			if (retval == SYSERR ) {
				//kprintf("rec_del fails\r\n\r\n");
				close(lfptr_del->lfdev);
				return SYSERR;
			}
			else {
				/* Update b's dir entry in directory a	*/
				//kprintf("Update %s's dir entry in %s\r\n", fname, dname);
				lfptr_del->lfdirptr = lf_dirptr;
				lfptr_del->lfpos	= pos*sizeof(struct ldentry);
				lfptr_del->lfpos     = 0;
				to = lfptr_del->lfname;
				from = fname;
				while ( (*to++ = *from++) != NULLCH ) {
					;
				}
				lfptr_del->lfinum    = LF_INULL;
				lfptr_del->lfdnum    = LF_DNULL;
				lfptr_del->lfbyte = &lfptr_del->lfdblock[LF_BLKSIZ];
				lfptr_del->lfibdirty = FALSE;
				lfptr_del->lfdbdirty = FALSE;
				signal(Lf_data.lf_mutex);
				write(lfptr_del->lfdev, (char *)ldptr, sizeof(struct ldentry));
				seek(lfptr_del->lfdev, 0);
				ldpaptr = (struct	ldentry	*)getmem(sizeof(struct ldentry));
				read(lfptr_del->lfdev, (char *)ldpaptr, sizeof(struct ldentry));
				freemem((char *)ldpaptr, sizeof(struct ldentry));
				wait(Lf_data.lf_mutex);
				//kprintf("recursively delete %s done\r\n", pname);
				return retval;
			}
		}
	}
	close(lfptr_del->lfdev);
	return SYSERR;
}
 
devcall	lfsdel (
	 struct	dentry	*devptr,	/* entry in device switch table	*/
	 char	*name				/* name of file (path include) to open */
	)
{
	struct	lfdir	*dirptr;	/* ptr to in-memory sector 0	*/
	did32	lfnext;				/* minor number of an unused	*/
								/*    file pseudo-device		*/
	int32	retval;				/* value returned from function	*/
	char	*from;		/* ptrs used during copy		*/
	int32	i, j, k;			/* general loop index			*/
	char	finalfname[LF_NAME_LEN];/* final file name to open		*/
	char	path[2][LF_NAME_LEN]; 			/* path names	*/
	uint32	pathnum;			/* record # dirs along the path	*/
	char	*nam, *cmp;			/* ptrs used during comparison	*/
	struct	ldentry	*ldptr;		/* ptr to dir entry for opening file*/
	struct	ldentry *ldpaptr;	/* ptr to dir entry used in search	*/
	bool8	found;				/* was the name found?			*/
	int32	pos;				/* where to add new dir entry	*/
	
	/* parse the file name into directory along the path */
	from = name;
	j = k = 0;
	if (from[0] != '/') {
		//kprintf("File name should start with '/'\r\n");
		close(lfptr_del->lfdev);
		return SYSERR;
	}
	for(i=1; from[i] != NULLCH; i++) {
		if(from[i] != '/') {
			if (j < 2) {
				path[j][k] = from[i];
			}
			finalfname[k++] = from[i];
		}
		else {
			if (j < 2) {
				path[j][k] = NULLCH;
				j ++;
			}
			finalfname[k] = NULLCH;
			k = 0;
		}
	}
	if (j < 2) {
		path[j][k] = NULLCH;
	}
	finalfname[k] = NULLCH; /* actual file name to open	*/
	pathnum = j;
	
	/* Check length of name file (leaving space for NULLCH */
	from = finalfname;
	for (i=0; i< LF_NAME_LEN; i++) {
		if (*from++ == NULLCH) {
			break;
		}
	}
	if (i >= LF_NAME_LEN) {		/* name is too long */
		//kprintf("Name is too long\r\n");
		close(lfptr_del->lfdev);
		return SYSERR;
	}
	
	/* If named file is already open, return SYSERR */
	lfnext = SYSERR;
	for (i=0; i<Nlfl; i++) {	/* search file pseudo-devices	*/
		lfptr_del = &lfltab[i];
		if (lfptr_del->lfstate == LF_FREE) {
			if (lfnext == SYSERR) {
				lfnext = i; /* record index */
			}
			continue;
		}
		/* Compare requested name to name of open file */
		nam = name;
		cmp = lfptr_del->lftotname;
		while(*nam != NULLCH) {
			if (*nam != *cmp) {
				break;
			}
			nam++;
			cmp++;
		}
		/* See if comparison succeeded */
		if ( (*nam==NULLCH) && (*cmp == NULLCH) ) {
			//kprintf("Cannot delete an open file\r\n");
			close(lfptr_del->lfdev);
			return SYSERR;
		}
	}
	if (lfnext == SYSERR) {	/* no slave file devices are available	*/
		//kprintf("No slave file devices are available\r\n");
		close(lfptr_del->lfdev);
		return SYSERR;
	}

	/* Obtain copy of Sector 0 if not already present in memory	*/
	dirptr = &Lf_data.lf_dir;
	wait(Lf_data.lf_mutex);
	if (! Lf_data.lf_dirpresent) {
	    retval = read(Lf_data.lf_dskdev,(char *)dirptr,LF_AREA_DIR);
	    if (retval == SYSERR ) {
		signal(Lf_data.lf_mutex);
		//kprintf("Cannot not read Sector 0 into memory\r\n");
		close(lfptr_del->lfdev);
		return SYSERR;
	    }
	}
	
	////kprintf("\r\nLoad root dir into memory\r\n");
	/* Initialize the local file pseudo-device */
	/* point to root directory entry*/
	lfptr_del = &lfltab[lfnext];
	lfptr_del->lfstate = LF_USED;
	lfptr_del->lfdirptr = &(dirptr->root_entry);
	/* File starts at position 0 */
	lfptr_del->lfpos     = 0;
	lfptr_del->lfname[0] = '/';
	lfptr_del->lfname[1] = NULLCH;
	/* Neither index block nor data block are initially valid	*/
	lfptr_del->lfinum    = LF_INULL;
	lfptr_del->lfdnum    = LF_DNULL;
	/* Initialize byte pointer to address beyond the end of the	*/
	/*	buffer (i.e., invalid pointer triggers setup)		*/
	lfptr_del->lfbyte = &lfptr_del->lfdblock[LF_BLKSIZ];
	lfptr_del->lfibdirty = FALSE;
	lfptr_del->lfdbdirty = FALSE;
	/* Load the root directory into memory, "open" root file 	*/	
	signal(Lf_data.lf_mutex);
	/* Search root directory to see if file exists */
	////kprintf("search root to see if %s exists\r\n", path[0]);
	found = FALSE;
	ldpaptr = (struct	ldentry	*)getmem(sizeof(struct ldentry));
	for (i=0; i<((lfptr_del->lfdirptr)->ld_size)/sizeof(struct ldentry); i++) {
		seek(lfptr_del->lfdev, i*sizeof(struct ldentry));
		read(lfptr_del->lfdev, (char *)ldpaptr, sizeof(struct ldentry));
		nam = path[0];
		cmp = ldpaptr->ld_name;
		////kprintf("ldpaptr->ld_name = %s\r\n", ldpaptr->ld_name);
		while(*nam != NULLCH) {
			if (*nam != *cmp) {
				break;
			}
			nam++;
			cmp++;
		}
		if ( (*nam==NULLCH) && (*cmp==NULLCH) ) { /* name found	*/
			found = TRUE;
			ldptr = ldpaptr;
			break;
		}
	}
	pos = i;
	wait(Lf_data.lf_mutex);
	/* Case #1 - dir a is not in root directory	*/
	if (! found) {
		/* open file direcotry in root directory, e.g. /a		*/
		signal(Lf_data.lf_mutex);
		//kprintf("No such directory %s exits!\r\n", path[0]);
		close(lfptr_del->lfdev);
		return SYSERR;
	}
	/* Case #2 - dir a is in root directory		*/
	else {
		/* open file direcotry in root directory		*/
		if (pathnum == 0) {
			//kprintf("only /%s, no more path\r\n", path[0]);
			if (ldpaptr->ld_type == LF_TYPE_DIR) {
				signal(Lf_data.lf_mutex);
				//kprintf("Cannot delete dir-type file, use rmdir\r\n");
				close(lfptr_del->lfdev);
				return SYSERR;
			}
			/* Take steps to delete file in root directory	*/
			////kprintf("Obtain first free i-blk and file's first i-blk\r\n");
			if (ldptr->ld_size != 0) {
				/* Obtain ID of first index block on free list */
				ifree = Lf_data.lf_dir.lfd_ifree;
				/* Record file's first i-block */
				firstib = ldptr->ld_ilist;
				/* Walk along index block list, disposing of each data block	*/
				/*  and clearing the corresponding pointer.		*/
				////kprintf("Free i-blk and d-blk for file %s begins\r\n", path[0]);
				iblock = (struct lfiblk *)getmem(sizeof(struct lfiblk));
				for (nextib=firstib; nextib!=ifree; nextib=iblock->ib_next) {
					/* Obtain a copy of current index block from disk	*/
					lfibget(Lf_data.lf_dskdev, nextib, iblock);
					////kprintf("Obtain a copy of current index block from disk done\r\n");
					/* Free each data block in the index block		*/
					for (i=0; i<LF_IBLEN; i++) {	/* for each d-block	*/
						/* Free the data block */
						nextdb = iblock->ib_dba[i];
						////kprintf("%d = iblock->ib_dba[%d];\r\n", nextdb, i);
						if (nextdb != LF_DNULL) {
							lfdbfree(Lf_data.lf_dskdev, nextdb);
							////kprintf("lfdbfree(%d, %d)\r\n", Lf_data.lf_dskdev, nextdb);
						}
						/* Clear entry in i-block for this d-block	*/
							iblock->ib_dba[i] = LF_DNULL;
					}
					////kprintf("Free all data blocks in the index block done\r\n");
					/* Clear offset (just to make debugging easier)		*/
					iblock->ib_offset = 0;
					/* For the last index block on the list, make it point	*/
					/*	to the current free list			*/
					if (iblock->ib_next == LF_INULL) {
						iblock->ib_next = ifree;
					}
					/* Write cleared i-block back to disk */
					lfibput(Lf_data.lf_dskdev, nextib, iblock);
				}
				////kprintf("Free i-blk and d-blk for file %s done\r\n", path[0]);
				/* Last index block on the file list now points to first node	*/
				/*   on the current free list. 	*/
				Lf_data.lf_dir.lfd_ifree = firstib;
			}
			/* delete a's dir entry in root directory	*/
			//kprintf("Delete %s's dir entry in root directory\r\n", path[0]);
			signal(Lf_data.lf_mutex);
			ldpaptr = (struct ldentry *)getmem(sizeof(struct ldentry));
			for (i=pos; i<(((lfptr_del->lfdirptr)->ld_size)/\
			sizeof(struct ldentry))-1; i++) {
				seek(lfptr_del->lfdev, (i+1)*sizeof(struct ldentry));			
				read(lfptr_del->lfdev, (char *)ldpaptr, sizeof(struct ldentry));
				seek(lfptr_del->lfdev, i*sizeof(struct ldentry));
				write(lfptr_del->lfdev, (char *)ldpaptr, sizeof(struct ldentry));
			}
			seek(lfptr_del->lfdev, i*sizeof(struct ldentry));
			from[0] = (char)EOF;
			write(lfptr_del->lfdev, from, 1);
			(lfptr_del->lfdirptr)->ld_size = (lfptr_del->lfdirptr)->ld_size - \
			sizeof(struct ldentry);
			write(Lf_data.lf_dskdev,(char *)dirptr,LF_AREA_DIR);
			freemem((char *)iblock, sizeof(struct lfiblk));
			close(lfptr_del->lfdev);
			return OK;
		}
		else if (pathnum > 0) {
			////kprintf("more dires after /%s\r\n", path[0]);
			if (ldpaptr->ld_type == LF_TYPE_FILE) {
				signal(Lf_data.lf_mutex);
				//kprintf("No such direcotry %s exists\r\n", path[0]);
				close(lfptr_del->lfdev);
				return SYSERR;
			}
			////kprintf("recursively open %s begins\r\n", name);
			retval = rec_del(name, ldptr);
			if (retval == SYSERR ) {
				//kprintf("rec_del fails\r\n\r\n");
				close(lfptr_del->lfdev);
				return SYSERR;
			}
			else {
				//kprintf("Update %s's dir entry in root direcotry\r\n", path[0]);
				lfptr_del->lfdirptr = &(dirptr->root_entry);
				lfptr_del->lfpos	= pos*sizeof(struct ldentry);
				lfptr_del->lfname[0] = '/';
				lfptr_del->lfname[1] = NULLCH;
				lfptr_del->lfinum    = LF_INULL;
				lfptr_del->lfdnum    = LF_DNULL;
				lfptr_del->lfbyte = &lfptr_del->lfdblock[LF_BLKSIZ];
				signal(Lf_data.lf_mutex);
				write(lfptr_del->lfdev, (char *)ldptr, sizeof(struct ldentry));
				seek(lfptr_del->lfdev, 0);
				ldpaptr = (struct	ldentry	*)getmem(sizeof(struct ldentry));
				read(lfptr_del->lfdev, (char *)ldpaptr, sizeof(struct ldentry));
				freemem((char *)ldpaptr, sizeof(struct ldentry));
				/* update root's dir entry in Sector 0		*/
				write(Lf_data.lf_dskdev,(char *)dirptr,LF_AREA_DIR);
				freemem((char *)ldpaptr, sizeof(struct ldentry));
				close(lfptr_del->lfdev);
				return OK;
			}
		}
	}
	close(lfptr_del->lfdev);
	return SYSERR;
}
